0%

SpringAI — 聊天记忆

概述

大型语言模型(LLM)是无状态的,这意味着它们不会保留之前交互的信息。当你希望在多次交互中保持上下文或状态时,这可能是一个限制。为了解决这个问题,Spring AI 提供了聊天记忆(Chat Memory)功能,允许你在与 LLM 的多次交互中存储和检索信息。

ChatMemory 抽象允许你实现各种类型的记忆,以支持不同的使用场景。消息的底层存储由 ChatMemoryRepository 处理,其唯一职责是存储和检索消息。ChatMemory 的实现负责决定保留哪些消息以及何时删除它们。策略示例包括:保留最近 N 条消息、保留一段时间内的消息,或将消息限制在一定的 token 数量以内。


聊天记忆 vs 聊天历史

在选择记忆类型之前,必须理解聊天记忆聊天历史之间的区别:

概念 说明
Chat Memory(聊天记忆) 大型语言模型保留并用于在整个对话过程中保持上下文感知的信息。
Chat History(聊天历史) 完整的对话历史,包括用户和模型之间交换的所有消息。

ChatMemory 抽象旨在管理聊天记忆。它允许你存储和检索与当前对话上下文相关的消息。然而,它并不适合存储完整的聊天历史。如果你需要维护所有交换消息的完整记录,应考虑使用其他方法,例如依赖 Spring Data 来高效存储和检索完整的聊天历史。


自动配置

Spring AI 会自动配置一个 ChatMemory Bean,你可以直接在应用程序中使用。默认情况下,它使用内存存储库InMemoryChatMemoryRepository)来存储消息,并使用 MessageWindowChatMemory 实现来管理对话历史。如果已经配置了其他存储库(例如 Cassandra、JDBC 或 Neo4j),Spring AI 将优先使用它们。


记忆类型(Memory Types)

ChatMemory 抽象允许你实现各种类型的记忆,以适应不同的使用场景。记忆类型的选择会显著影响应用程序的性能和行为。本节介绍 Spring AI 提供的内置记忆类型及其特性。

MessageWindowChatMemory

MessageWindowChatMemory 维护一个滑动窗口式的消息队列,最大大小由指定参数控制。当消息数量超过最大值时,较旧的消息会被逐出,同时始终保留 SystemMessage 实例。默认窗口大小为 20 条消息

这是 Spring AI 用于自动配置 ChatMemory Bean 的默认记忆类型。

逐出策略

当需要进行消息逐出时,MessageWindowChatMemory 始终移除完整的对话轮次(turn),而不是在轮次中间截断。一个轮次(turn)UserMessage 开始,包括所有后续的助手回复、工具调用和工具响应,直到下一个 UserMessage 为止。

如果原始逐出点落在非用户消息上(例如,在工具调用交换过程中的助手回复),则截断位置会向前推进到下一个 UserMessage,以确保保留的窗口始终从完整的轮次开始。

这意味着 maxMessages 是存储消息数量的上限——当需要进行边界对齐时,实际数量可能会略低一些。


记忆存储库(Chat Memory Repositories)

Spring AI 提供了 ChatMemoryRepository 抽象用于存储聊天记忆。本节介绍 Spring AI 提供的内置存储库及其使用方法,你也可以根据需要实现自己的存储库。


1. InMemoryChatMemoryRepository(内存存储库)

InMemoryChatMemoryRepository 使用 ConcurrentHashMap 在内存中存储消息。

默认情况下,如果没有配置其他存储库,Spring AI 会自动配置一个 InMemoryChatMemoryRepository 类型的 ChatMemoryRepository Bean,你可以直接在应用程序中使用。

手动创建示例:

1
ChatMemoryRepository repository = new InMemoryChatMemoryRepository();

2. JdbcChatMemoryRepository(关系型数据库存储库)

JdbcChatMemoryRepository 是一个内置实现,使用 JDBC 将消息存储在关系型数据库中。它开箱即用地支持多种数据库,适用于需要持久化存储聊天记忆的应用程序。

消息按升序(从旧到新)检索,这是 LLM 对话历史的预期格式。排序由 sequence_id 列维护,该列记录每条消息在对话中的位置。每条消息还存储一个创建时间戳,可通过消息元数据中的 JdbcChatMemoryRepository.CONVERSATION_TS 键以 java.time.Instant 的形式访问,方便应用程序显示消息创建时间。

⚠️ 注意: JdbcChatMemoryRepository 不支持工具调用消息AssistantMessage 实例中包含的工具调用和 ToolResponseMessage 实例在保存时会被静默过滤掉,不会出现在检索到的对话历史中。如果你的应用程序使用了工具调用,请考虑使用 Spring AI Session 项目和 JDBC Session Store,它们可以正确持久化所有消息类型。

添加依赖

Maven:

1
2
3
4
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-jdbc-chat-memory-repository</artifactId>
</dependency>

Gradle:

1
implementation 'org.springframework.ai:spring-ai-jdbc-chat-memory-repository'

自动配置

Spring AI 为 JdbcChatMemoryRepository 提供了自动配置,你可以直接在应用程序中使用。

手动创建示例:

1
2
3
4
ChatMemoryRepository repository = JdbcChatMemoryRepository.builder()
.jdbcTemplate(jdbcTemplate)
.dialect(JdbcChatMemoryRepositoryDialect.from(dataSource))
.build();

时间戳访问

每条存储的消息都记录了其创建时间。时间戳可通过 JdbcChatMemoryRepository.CONVERSATION_TS 键以 java.time.Instant 的形式在消息元数据中访问:

1
2
Instant createdAt = (Instant) message.getMetadata()
.get(JdbcChatMemoryRepository.CONVERSATION_TS);

当对话再次保存时,时间戳会被保留,因此消息在其整个生命周期内都保持原始创建时间。

支持的数据库

Spring AI 通过方言(Dialect)抽象支持多种关系型数据库:

  • PostgreSQL
  • MySQL / MariaDB
  • SQL Server
  • HSQLDB
  • Oracle Database

使用 JdbcChatMemoryRepositoryDialect.from(DataSource) 可以从 JDBC URL 自动检测正确的方言。你可以通过实现 JdbcChatMemoryRepositoryDialect 接口来扩展对其他数据库的支持。

配置属性

属性 描述 默认值
spring.ai.chat.memory.repository.jdbc.initialize-schema 控制何时初始化模式。可选值:embedded(默认)、alwaysnever embedded
spring.ai.chat.memory.repository.jdbc.schema 用于初始化的模式脚本位置。支持 classpath: URL 和平台占位符 classpath:org/springframework/ai/chat/memory/repository/jdbc/schema-@@platform@@.sql
spring.ai.chat.memory.repository.jdbc.platform 如果使用了 @@platform@@ 占位符,则使用此平台值 自动检测

自动配置会在启动时自动创建 SPRING_AI_CHAT_MEMORY 表,使用针对你的数据库定制的 SQL 脚本。默认情况下,模式初始化仅针对嵌入式数据库(H2、HSQL、Derby 等)运行。

1
2
3
4
5
# 控制模式初始化
spring.ai.chat.memory.repository.jdbc.initialize-schema=always

# 覆盖模式脚本位置
spring.ai.chat.memory.repository.jdbc.schema=classpath:custom-schema.sql

要为新的数据库添加支持,请实现 JdbcChatMemoryRepositoryDialect 接口,并提供用于选择、插入和删除消息的 SQL。然后你可以将自定义方言传递给存储库构建器。


3. CassandraChatMemoryRepository(Cassandra 存储库)

CassandraChatMemoryRepository 使用 Apache Cassandra 存储消息。它适用于需要持久化存储聊天记忆的应用程序,特别是在可用性、持久性、可扩展性方面,以及利用生存时间(TTL)特性时。

CassandraChatMemoryRepository 具有时间序列模式,记录所有过去的聊天窗口,对于治理和审计非常有价值。建议将生存时间设置为某个值,例如三年。

消息按升序时间戳(从旧到新)检索,这是 LLM 对话历史的预期格式。

添加依赖

Maven:

1
2
3
4
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-cassandra-chat-memory-repository</artifactId>
</dependency>

Gradle:

1
implementation 'org.springframework.ai:spring-ai-cassandra-chat-memory-repository'

自动配置

Spring AI 为 CassandraChatMemoryRepository 提供了自动配置,你可以直接在应用程序中使用。

手动创建示例:

1
2
3
ChatMemoryRepository repository = CassandraChatMemoryRepository.builder()
.config(cassandraConfig)
.build();

配置属性

属性 描述 默认值
spring.cassandra.contactPoints 用于启动集群发现的主机 127.0.0.1
spring.cassandra.port Cassandra 原生协议端口 9042
spring.cassandra.localDatacenter 要连接的 Cassandra 数据中心 datacenter1
spring.ai.chat.memory.cassandra.time-to-live 写入 Cassandra 的消息的 TTL
spring.ai.chat.memory.cassandra.keyspace Cassandra 键空间 springframework
spring.ai.chat.memory.cassandra.messages-column 消息的 Cassandra 列名 springframework
spring.ai.chat.memory.cassandra.table Cassandra 表名 ai_chat_memory
spring.ai.chat.memory.cassandra.initialize-schema 是否在启动时初始化模式 true

自动配置会自动创建 ai_chat_memory 表。你可以通过将 spring.ai.chat.memory.repository.cassandra.initialize-schema 设置为 false 来禁用模式初始化。

⚠️ 注意: CassandraChatMemoryRepository 不支持工具调用消息AssistantMessage 实例中包含的工具调用和 ToolResponseMessage 实例在保存时会被静默过滤掉。如果你的应用程序使用了工具调用,请考虑使用 Spring AI Session 项目。


4. Neo4jChatMemoryRepository(Neo4j 图数据库存储库)

Neo4jChatMemoryRepository 是一个内置实现,使用 Neo4j 将聊天消息存储为属性图数据库中的节点和关系。它适用于希望利用 Neo4j 的图能力进行聊天记忆持久化的应用程序。

消息按升序消息索引(从旧到新)检索,这是 LLM 对话历史的预期格式。

添加依赖

Maven:

1
2
3
4
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-neo4j-chat-memory-repository</artifactId>
</dependency>

Gradle:

1
implementation 'org.springframework.ai:spring-ai-neo4j-chat-memory-repository'

自动配置

Spring AI 为 Neo4jChatMemoryRepository 提供了自动配置,你可以直接在应用程序中使用。

手动创建示例:

1
2
3
ChatMemoryRepository repository = Neo4jChatMemoryRepository.builder()
.driver(neo4jDriver)
.build();

配置属性

属性 描述 默认值
spring.ai.chat.memory.repository.neo4j.session-label 存储对话会话的节点标签 Session
spring.ai.chat.memory.repository.neo4j.message-label 存储消息的节点标签 Message
spring.ai.chat.memory.repository.neo4j.tool-call-label 存储工具调用的节点标签 ToolCall
spring.ai.chat.memory.repository.neo4j.metadata-label 存储消息元数据的节点标签 Metadata
spring.ai.chat.memory.repository.neo4j.tool-response-label 存储工具响应的节点标签 ToolResponse
spring.ai.chat.memory.repository.neo4j.media-label 存储与消息关联的媒体的节点标签 Media

Neo4j 存储库会自动确保为对话 ID 和消息索引创建索引以优化性能。如果你使用自定义标签,也会为这些标签创建索引。不需要手动初始化模式,但你需要确保 Neo4j 实例可被应用程序访问。


5. MongoChatMemoryRepository(MongoDB 文档存储库)

MongoChatMemoryRepository 是一个内置实现,使用 MongoDB 存储消息。它适用于需要灵活的文档型数据库进行聊天记忆持久化的应用程序。

消息按升序时间戳(从旧到新)检索,这是 LLM 对话历史的预期格式。这种排序方式在所有聊天记忆存储库实现中保持一致。

⚠️ 注意: MongoChatMemoryRepository 不支持工具调用消息AssistantMessage 实例中包含的工具调用和 ToolResponseMessage 实例在保存时会被静默过滤掉。如果你的应用程序使用了工具调用,请考虑使用 Spring AI Session 项目。

添加依赖

Maven:

1
2
3
4
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mongo-chat-memory-repository</artifactId>
</dependency>

Gradle:

1
implementation 'org.springframework.ai:spring-ai-mongo-chat-memory-repository'

自动配置

Spring AI 为 MongoChatMemoryRepository 提供了自动配置,你可以直接在应用程序中使用。

手动创建示例:

1
2
3
ChatMemoryRepository repository = MongoChatMemoryRepository.builder()
.mongoTemplate(mongoTemplate)
.build();

配置属性

属性 描述 默认值
spring.ai.chat.memory.repository.mongo.create-indices 是否在启动时自动创建或重建索引。注意:更改 TTL 值会删除 TTL 索引并重新创建 false
spring.ai.chat.memory.repository.mongo.ttl 写入 MongoDB 的消息的 TTL(秒)。如果未设置,消息将永久存储 0

自动配置会在启动时自动创建 ai_chat_memory 集合(如果尚不存在)。


6. RedisChatMemoryRepository(Redis 存储库)

RedisChatMemoryRepository 是一个内置实现,使用 Redis Stack(包含 Redis Query Engine 和 RedisJSON)存储聊天消息。它适用于需要高性能、低延迟的聊天记忆持久化,并支持可选的 TTL(生存时间)和高级查询功能的应用程序。

该存储库将消息存储为 JSON 文档,并创建搜索索引以实现高效查询。它还通过 AdvancedRedisChatMemoryRepository 接口提供扩展的查询功能,支持按内容、类型、时间范围和元数据进行搜索。

消息按升序时间戳(从旧到新)检索,这是 LLM 对话历史的预期格式。

添加依赖

Maven:

1
2
3
4
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-redis-chat-memory-repository</artifactId>
</dependency>

Gradle:

1
implementation 'org.springframework.ai:spring-ai-redis-chat-memory-repository'

自动配置

Spring AI 为 RedisChatMemoryRepository 提供了自动配置,你可以直接在应用程序中使用。

手动创建示例:

1
2
3
ChatMemoryRepository repository = RedisChatMemoryRepository.builder()
.redisClient(redisClient)
.build();

配置属性

属性 描述 默认值
spring.ai.chat.memory.redis.host Redis 服务器主机 localhost
spring.ai.chat.memory.redis.port Redis 服务器端口 6379
spring.ai.chat.memory.redis.index-name Redis 搜索索引名称 chat-memory-idx
spring.ai.chat.memory.redis.key-prefix 聊天记忆条目的键前缀 chat-memory:
spring.ai.chat.memory.redis.time-to-live 聊天记忆条目的生存时间(例如 24h30d 永不过期
spring.ai.chat.memory.redis.initialize-schema 是否在启动时初始化 Redis 模式 true
spring.ai.chat.memory.redis.max-conversation-ids 返回的最大对话 ID 数量 1000
spring.ai.chat.memory.redis.max-messages-per-conversation 每个对话返回的最大消息数 1000

高级查询

RedisChatMemoryRepository 还实现了 AdvancedRedisChatMemoryRepository 接口,提供扩展的查询功能:

1
2
3
4
5
6
7
8
9
10
11
AdvancedRedisChatMemoryRepository advancedRepo = (AdvancedRedisChatMemoryRepository) repository;

// 按内容搜索消息
List<Message> results = advancedRepo.searchMessages("conversationId", "搜索关键词");

// 按时间范围查询
List<Message> results = advancedRepo.searchMessagesByTimeRange(
"conversationId",
Instant.now().minus(Duration.ofHours(1)),
Instant.now()
);

要启用对自定义元数据字段的高效查询,可以配置元数据字段定义:

1
2
3
4
5
6
7
8
RedisChatMemoryRepository repository = RedisChatMemoryRepository.builder()
.redisClient(redisClient)
.metadataFieldDefinitions(List.of(
new MetadataFieldDefinition("category", FieldType.TAG),
new MetadataFieldDefinition("content", FieldType.TEXT),
new MetadataFieldDefinition("score", FieldType.NUMERIC)
))
.build();

支持的字段类型:

  • tag:用于精确匹配过滤
  • text:用于全文搜索
  • numeric:用于范围查询

自动配置会在启动时自动创建 Redis 搜索索引(如果尚不存在)。你可以通过设置 spring.ai.chat.memory.redis.initialize-schema=false 来禁用此行为。

前置要求

  • Redis Stack 7.0 或更高版本(包含 Redis Query Engine 和 RedisJSON 模块)
  • Jedis 客户端库(已作为依赖项包含)

在 ChatClient 中使用 ChatMemory

使用 ChatClient API 时,你可以提供 ChatMemory 实现,以在多次交互中保持对话上下文。

Spring AI 提供了几个内置的 Advisor,你可以根据需要使用它们来配置 ChatClient 的记忆行为:

MessageChatMemoryAdvisor

该 Advisor 使用提供的 ChatMemory 实现来管理对话记忆。在每次交互时,它从记忆中检索对话历史,并将其作为消息集合包含在提示中。

VectorStoreChatMemoryAdvisor

该 Advisor 使用提供的 VectorStore 实现来管理对话记忆。在每次交互时,它从向量存储中检索对话历史,并将其作为纯文本附加到系统消息中。

配置示例

如果你想将 MessageWindowChatMemoryMessageChatMemoryAdvisor 一起使用,可以这样配置:

1
2
3
4
5
6
7
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(
MessageChatMemoryAdvisor.builder(chatMemory)
.conversationId("conversation-123")
.build()
)
.build();

当执行对 ChatClient 的调用时,记忆会被 MessageChatMemoryAdvisor 自动管理。对话历史将根据指定的对话 ID 从记忆中检索。

VectorStoreChatMemoryAdvisor 使用默认模板将检索到的对话记忆增强到系统消息中。你可以通过 .promptTemplate() 构建器方法提供自己的 PromptTemplate 对象来自定义此行为。

自定义 PromptTemplate 可以使用任何 TemplateRenderer 实现(默认使用基于 StringTemplate 引擎的 StPromptTemplate)。重要的要求是模板必须包含以下两个占位符:

  • instructions — 接收原始系统消息
  • long_term_memory — 接收检索到的对话记忆
1
2
3
4
5
6
7
8
9
10
11
PromptTemplate customTemplate = new PromptTemplate(
"System: {instructions}\n\nPrevious conversation:\n{long_term_memory}"
);

ChatClient chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(
VectorStoreChatMemoryAdvisor.builder(vectorStore)
.promptTemplate(customTemplate)
.build()
)
.build();

直接使用 ChatModel

如果你直接使用 ChatModel 而不是 ChatClient,可以显式地管理记忆:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 将消息添加到记忆
chatMemory.add(conversationId, userMessage);

// 从记忆中检索消息
List<Message> messages = chatMemory.get(conversationId);

// 调用模型
ChatResponse response = chatModel.call(
new Prompt(messages)
);

// 将助手回复添加到记忆
chatMemory.add(conversationId, response.getResult().getOutput());